1use crate::copp::copp3::Copp3Result;
26use crate::copp::copp3::formulation::{Topp3Problem, get_weight_a_topp3};
27use crate::copp::copp3::opt3::clarabel_constraints::{
28 clarabel_standard_capacity_topp3, clarabel_standard_constraint_topp3,
29};
30use crate::copp::{ClarabelOptions, clarabel_to_copp3_solution};
31use crate::diag::{
32 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
33 check_boundary_state_copp3_valid, check_s_interval_valid, format_duration_human,
34};
35use clarabel::algebra::CscMatrix;
36use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
37use core::f64;
38
39pub fn topp3_lp(
58 problem: &Topp3Problem,
59 options: &ClarabelOptions,
60) -> Result<Copp3Result, CoppError> {
61 let (result, solution) = topp3_lp_expert(problem, options)?;
62 result.ok_or_else(|| CoppError::ClarabelSolverStatus("topp3_lp".into(), solution.status))
63}
64
65pub fn topp3_lp_expert(
89 problem: &Topp3Problem,
90 options: &ClarabelOptions,
91) -> Result<(Option<Copp3Result>, DefaultSolution<f64>), CoppError> {
92 match options.verbosity() {
93 Verbosity::Silent => topp3_lp_core(problem, (options, SilentVerboser)),
94 Verbosity::Summary => topp3_lp_core(problem, (options, SummaryVerboser::new())),
95 Verbosity::Debug => topp3_lp_core(problem, (options, DebugVerboser::new())),
96 Verbosity::Trace => topp3_lp_core(problem, (options, TraceVerboser::new())),
97 }
98}
99
100fn topp3_lp_core(
111 problem: &Topp3Problem,
112 options_verboser: (&ClarabelOptions, impl Verboser),
113) -> Result<(Option<Copp3Result>, DefaultSolution<f64>), CoppError> {
114 let (options, mut verboser) = options_verboser;
115 let idx_s_start = problem.idx_s_start;
116 let a_boundary = problem.a_boundary;
117 let b_boundary = problem.b_boundary;
118 let num_stationary = problem.num_stationary;
119 if verboser.is_enabled(Verbosity::Summary) {
120 verboser.record_start_time();
121 }
122 if verboser.is_enabled(Verbosity::Trace) {
123 let settings = options.clarabel_settings();
124 crate::verbosity_log!(
125 crate::diag::Verbosity::Summary,
126 "topp3_lp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
127 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
128 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
129 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
130 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
131 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
132 settings.tol_gap_rel,
133 settings.tol_feas,
134 settings.max_iter,
135 settings.verbose
136 );
137 }
138
139 check_boundary_state_copp3_valid(a_boundary, b_boundary)?;
141 let n = problem.a_linearization.len() - 1;
142 let idx_s_final = idx_s_start + n;
143 if verboser.is_enabled(Verbosity::Summary) {
144 crate::verbosity_log!(
145 crate::diag::Verbosity::Summary,
146 "\ntopp3_lp started: {} <= idx_s <= {}, s_len = {}, num_stationary={:?}.",
147 idx_s_start,
148 idx_s_final,
149 problem.a_linearization.len(),
150 num_stationary
151 );
152 }
153 check_s_interval_valid("topp3_lp", idx_s_start, idx_s_final)?;
154 let (capacity_val, capacity_b, capacity_cones) =
160 clarabel_standard_capacity_topp3(problem.constraints, (idx_s_start, idx_s_final));
161 if verboser.is_enabled(Verbosity::Debug) {
162 crate::verbosity_log!(
163 crate::diag::Verbosity::Summary,
164 "topp3_lp: capacity estimate standard(val={capacity_val}, b={capacity_b}, cone={capacity_cones}), n_var={}",
165 2 * (n + 1)
166 );
167 }
168 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(capacity_cones);
169 let mut row = Vec::<usize>::with_capacity(capacity_val);
170 let mut col = Vec::<usize>::with_capacity(capacity_val);
171 let mut val = Vec::<f64>::with_capacity(capacity_val);
172 let mut b = Vec::<f64>::with_capacity(capacity_b);
173 if verboser.is_enabled(Verbosity::Trace) {
174 crate::verbosity_log!(
175 crate::diag::Verbosity::Summary,
176 "topp3_lp: allocated capacities row/col/val/b/cones <= {capacity_val}/{capacity_val}/{capacity_val}/{capacity_b}/{capacity_cones}",
177 );
178 }
179
180 let s = problem.constraints.s_vec(idx_s_start, idx_s_final + 1)?;
182 let row_before_std = row.len();
183 let col_before_std = col.len();
184 let val_before_std = val.len();
185 let b_before_std = b.len();
186 let cones_before_std = cones.len();
187 clarabel_standard_constraint_topp3(
188 problem,
189 &s,
190 (&mut row, &mut col, &mut val, &mut b, &mut cones),
191 num_stationary,
192 &verboser,
193 )?;
194 if verboser.is_enabled(Verbosity::Trace) {
195 crate::verbosity_log!(
196 crate::diag::Verbosity::Summary,
197 "topp3_lp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
198 row.len() - row_before_std,
199 col.len() - col_before_std,
200 val.len() - val_before_std,
201 b.len() - b_before_std,
202 cones.len() - cones_before_std
203 );
204 }
205
206 let n_var = 2 * (n + 1);
208 let row_len = row.len();
209 let col_len = col.len();
210 let val_len = val.len();
211 let b_len = b.len();
212 let cones_len = cones.len();
213 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
214 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
216 let q_object = clarabel_q_object_topp3_lp(&s, num_stationary, n_var);
217 if verboser.is_enabled(Verbosity::Trace) {
218 let (q_min, q_max) = q_object
219 .iter()
220 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
221 (mn.min(v), mx.max(v))
222 });
223 crate::verbosity_log!(
224 crate::diag::Verbosity::Summary,
225 "topp3_lp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}, q_range=[{}, {}]",
226 b_len,
227 n_var,
228 a_csc.nnz(),
229 p_object.nnz(),
230 q_min,
231 q_max
232 );
233 }
234 if verboser.is_enabled(Verbosity::Summary) {
235 crate::verbosity_log!(
236 crate::diag::Verbosity::Summary,
237 "topp3_lp: ready to solve with row/col/val/b/cones = {row_len}/{col_len}/{val_len}/{b_len}/{cones_len} and n_var = {n_var}.",
238 );
239 }
240 let settings = options.clarabel_settings().clone();
242 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
243 .map_err(|e| CoppError::ClarabelSolverError("topp3_lp".into(), e))?;
244 solver.solve();
245 let solution = solver.solution;
246 if verboser.is_enabled(Verbosity::Summary) {
247 crate::verbosity_log!(
248 crate::diag::Verbosity::Summary,
249 "topp3_lp: solve done, status = {:?}, elapsed = {}.",
250 solution.status,
251 format_duration_human(verboser.elapsed())
252 );
253 }
254 if verboser.is_enabled(Verbosity::Trace) {
255 let show = solution.x.len().min(3);
256 crate::verbosity_log!(
257 crate::diag::Verbosity::Summary,
258 "topp3_lp: solution x_len={}, head={:?}",
259 solution.x.len(),
260 &solution.x[0..show]
261 );
262 }
263 let result = if options.is_allow(solution.status) {
264 let (a, b) =
265 clarabel_to_copp3_solution(&solution.x.as_slice()[0..2 * (n + 1)], &s, num_stationary);
266 Some((a, b, num_stationary))
267 } else {
268 None
269 };
270 if verboser.is_enabled(Verbosity::Trace) {
271 crate::verbosity_log!(
272 crate::diag::Verbosity::Summary,
273 "topp3_lp: allow(status)={}, extracted_profile={}",
274 options.is_allow(solution.status),
275 if result.is_some() {
276 "Some((a,b,num_stationary))"
277 } else {
278 "None"
279 }
280 );
281 }
282 Ok((result, solution))
283}
284
285#[inline(always)]
295fn clarabel_q_object_topp3_lp(s: &[f64], num_stationary: (usize, usize), n_var: usize) -> Vec<f64> {
296 let mut q_object = get_weight_a_topp3(s, num_stationary);
297 q_object.iter_mut().for_each(|q_i| *q_i = -*q_i);
299 q_object.resize(n_var, 0.0);
300 q_object
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306 use crate::copp::ClarabelOptionsBuilder;
307 use crate::copp::InterpolationMode;
308 use crate::copp::copp2::stable::basic::{Topp2ProblemBuilder, s_to_t_topp2};
309 use crate::copp::copp2::stable::reach_set2::ReachSet2OptionsBuilder;
310 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
311 use crate::copp::copp3::stable::basic::{Topp3ProblemBuilder, s_to_t_topp3, t_to_s_topp3};
312 use crate::path::add_symmetric_axial_limits_for_test;
313 use crate::robot::robot_core::Robot;
314 use nalgebra::DMatrix;
315 use rand::RngExt;
316 use std::time::Instant;
317
318 #[test]
319 fn test_topp3_lp() -> Result<(), CoppError> {
320 run_test_topp3_lp_repeated(1, false)
321 }
322
323 #[test]
326 #[ignore = "slow"]
327 fn test_topp3_lp_robust() -> Result<(), CoppError> {
328 run_test_topp3_lp_repeated(100, true)
329 }
330
331 fn run_one_topp3_lp_case(
332 options_lp: &ClarabelOptions,
333 ) -> Result<(f64, f64, f64, f64, f64, usize), CoppError> {
334 let n: usize = 1000;
335 let dim = 7;
336 let mut rng = rand::rng();
337 let omega = (0..dim)
338 .map(|_| rng.random_range(0.1..(2.0 * f64::consts::PI)))
339 .collect::<Vec<f64>>();
340 let phi = (0..dim)
341 .map(|_| rng.random_range(0.0..(2.0 * f64::consts::PI)))
342 .collect::<Vec<f64>>();
343
344 let mut robot = Robot::with_capacity(dim, n);
345 let s = DMatrix::<f64>::from_fn(1, n, |_, j| {
346 (j as f64
347 + (if 0 < j && 2 * j < n { 0.5 } else { 0.0 }
348 + if n > j && 2 * j > n { 0.5 } else { 0.0 })
349 * j as f64
350 / n as f64)
351 * (1.0 / (n - 1) as f64)
352 });
353 let q = DMatrix::<f64>::from_fn(dim, n, |i, j| (omega[i] * s[j] + phi[i]).sin());
354 let dq =
355 DMatrix::<f64>::from_fn(dim, n, |i, j| omega[i] * (omega[i] * s[j] + phi[i]).cos());
356 let ddq = DMatrix::<f64>::from_fn(dim, n, |i, j| {
357 -omega[i] * omega[i] * (omega[i] * s[j] + phi[i]).sin()
358 });
359 let dddq = DMatrix::<f64>::from_fn(dim, n, |i, j| {
360 -omega[i] * omega[i] * omega[i] * (omega[i] * s[j] + phi[i]).cos()
361 });
362 robot.with_s(&s.as_view())?;
363 robot.with_q(
364 &q.as_view(),
365 &dq.as_view(),
366 &ddq.as_view(),
367 Some(&dddq.as_view()),
368 0,
369 )?;
370 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
371
372 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
373 let start = Instant::now();
374 let options_ra = ReachSet2OptionsBuilder::new()
375 .lp_feas_tol(1E-9)
376 .a_cmp_abs_tol(1E-9)
377 .a_cmp_rel_tol(1E-9)
378 .build()?;
379 let a_profile_ra = topp2_ra(&topp2_problem, &options_ra)?;
380 let time_topp_ra = start.elapsed().as_secs_f64() * 1E3;
381 let (t_motion_ra, _) = s_to_t_topp2(s.as_slice(), &a_profile_ra, 0.0);
382
383 let start = Instant::now();
384 robot.constraints.amax_substitute(&a_profile_ra, 0)?;
385 let topp3_problem =
386 Topp3ProblemBuilder::new(&mut robot, 0, &a_profile_ra, (0.0, 0.0), (0.0, 0.0))
387 .with_num_stationary_max(2)
388 .build_with_linearization()?;
389 let (a_profile, b_profile, num_stationary) = topp3_lp(&topp3_problem, options_lp)?;
390 let time_topp3_lp = start.elapsed().as_secs_f64() * 1E3;
391 let start = Instant::now();
392 let (t_motion_lp, t_s) =
393 s_to_t_topp3(s.as_slice(), &a_profile, &b_profile, num_stationary, 0.0);
394 let s_t = t_to_s_topp3(
395 s.as_slice(),
396 &a_profile,
397 &b_profile,
398 num_stationary,
399 &t_s,
400 InterpolationMode::UniformTimeGrid(0.0, 1E-3, true),
401 );
402 let time_interpolation = start.elapsed().as_secs_f64() * 1E3;
403 Ok((
404 time_topp_ra,
405 time_topp3_lp,
406 time_interpolation,
407 t_motion_ra,
408 t_motion_lp,
409 s_t.len(),
410 ))
411 }
412
413 fn run_test_topp3_lp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
414 let options_lp = ClarabelOptionsBuilder::new()
415 .allow_almost_solved(true)
416 .build()?;
417
418 let mut tc_sum_ra = 0.0;
419 let mut tc_sum_lp = 0.0;
420 let mut tf_sum_ra = 0.0;
421 let mut tf_sum_lp = 0.0;
422 for i_exp in 0..n_exp {
423 let (
424 time_topp_ra,
425 time_topp3_lp,
426 time_interpolation,
427 t_motion_ra,
428 t_motion_lp,
429 s_t_len,
430 ) = run_one_topp3_lp_case(&options_lp)?;
431
432 if flag_print_step {
433 crate::verbosity_log!(
434 crate::diag::Verbosity::Summary,
435 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_interpolation = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, s_t.len() = {}",
436 i_exp + 1,
437 time_topp_ra,
438 time_topp3_lp,
439 time_interpolation,
440 t_motion_ra,
441 t_motion_lp,
442 s_t_len,
443 );
444 }
445
446 tc_sum_ra += time_topp_ra;
447 tc_sum_lp += time_topp3_lp;
448 tf_sum_ra += t_motion_ra;
449 tf_sum_lp += t_motion_lp;
450 }
451
452 crate::verbosity_log!(
453 crate::diag::Verbosity::Summary,
454 "Average over {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}",
455 n_exp,
456 tc_sum_ra / n_exp as f64,
457 tc_sum_lp / n_exp as f64,
458 tf_sum_ra / n_exp as f64,
459 tf_sum_lp / n_exp as f64,
460 );
461
462 Ok(())
463 }
464}